package com.opensource.component.query.wrapper;

import com.baomidou.mybatisplus.core.conditions.SharedString;
import com.baomidou.mybatisplus.core.conditions.segments.MergeSegments;
import com.baomidou.mybatisplus.core.enums.SqlKeyword;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.opensource.component.query.exception.MpExtendException;
import com.opensource.component.query.matedata.TableMetadataHelper;
import com.opensource.component.query.script.JoinSegment;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author ZL since on 2021/12/18 下午11:18
 */
public final class LambdaJoinWrapper<M, J> extends
    AbstractExtendWrapper<J, LambdaJoinWrapper<M, J>> {


  /**
   * 主表的 Wrapper
   */
  protected LambdaExtendWrapper<M> masterTableWrapper;

  /**
   * 不建议直接 new 该实例，使用 Wrappers.lambdaQuery(entity)
   */
  LambdaJoinWrapper(Class<J> joinTableClass, LambdaExtendWrapper<M> masterTableWrapper) {
    super(joinTableClass);
    super.setEntityClass(joinTableClass);
    this.masterTableWrapper = masterTableWrapper;
    this.paramNameValuePairs = masterTableWrapper.getParamNameValuePairs();
    this.paramNameSeq = masterTableWrapper.getParamNameSeq();
    setTableAlias(TableMetadataHelper.tableAlias(joinTableClass));
  }

  /**
   * 提供给JOIN条件内部条件单独作用空间
   */
  LambdaJoinWrapper(LambdaExtendWrapper<M> masterTableWrapper, J entity, Class<J> joinTableClass,
      AtomicInteger paramNameSeq,
      Map<String, Object> paramNameValuePairs, MergeSegments mergeSegments,
      SharedString lastSql, SharedString sqlComment, SharedString sqlFirst) {
    super(joinTableClass);
    this.masterTableWrapper = masterTableWrapper;
    super.setEntity(entity);
    super.setEntityClass(joinTableClass);
    this.paramNameSeq = paramNameSeq;
    this.paramNameValuePairs = paramNameValuePairs;
    this.expression = mergeSegments;
    this.lastSql = lastSql;
    this.sqlComment = sqlComment;
    this.sqlFirst = sqlFirst;
    setTableAlias(TableMetadataHelper.tableAlias(joinTableClass));
  }


  public LambdaJoinWrapper<M, J> eq(SFunction<J, Object> joinTableField,
      SFunction<M, Object> masterTableField) {
    // 获取主表字段名带有别名
    String masterColumn = TableMetadataHelper.tableMetadata(masterTableWrapper.getEntityClass())
        .getColumnAndAlias(masterTableField);
    appendSqlSegments(columnToSqlSegment(joinTableField), SqlKeyword.EQ,
        () -> masterColumn);
    return this;
  }

  /**
   * 获取字段名
   */
  @Override
  protected String columnToString(SFunction<J, ?> column, boolean onlyColumn) {
    final String columnToString = super.columnToString(column, onlyColumn);
    return tableAlias + Constants.DOT + columnToString;
  }

  @Override
  public String getSqlSelect() {
    throw new MpExtendException("Join table could not getSqlSelect");
  }

  /**
   * 用于生成嵌套 SQL
   * <p>故 sqlSelect 不向下传递</p>
   */
  @Override
  protected LambdaJoinWrapper<M, J> instance() {
    return new LambdaJoinWrapper<>(masterTableWrapper, getEntity(),
        getEntityClass(), paramNameSeq,
        paramNameValuePairs,
        new MergeSegments(), SharedString.emptyString(), SharedString.emptyString(),
        SharedString.emptyString());
  }

  @Override
  public void clear() {
    super.clear();
    masterTableWrapper = null;
  }


  /**
   * left join 处理
   */
  protected void doLeftJoin() {
    buildJoinSql(JoinSegment.LEFT_JOIN);
  }

  /**
   * right join 处理
   */
  protected void doRightJoin() {
    buildJoinSql(JoinSegment.RIGHT_JOIN);
  }

  /**
   * join 处理
   */
  protected void doInnerJoin() {
    buildJoinSql(JoinSegment.INNER_JOIN);
  }

  /**
   * 构建join SQL
   *
   * @param joinSegment 需要构建的SQL枚举
   */
  private void buildJoinSql(JoinSegment joinSegment) {
    // 获取需要join表别名
    SharedString sharedString = SharedString.emptyString();
    sharedString.setStringValue(String
        .format(joinSegment.getSql(), finalTableName, getJoinOnCondition()));
    masterTableWrapper.addJoinSql(sharedString);
  }

  /**
   * 从join的on关联条件上获取join的SQL条件，使用MP的SQL片段方式
   */
  private String getJoinOnCondition() {
    return expression.getNormal().getSqlSegment();
  }
}
